package cn.kgc.filter;

import cn.hutool.json.JSONUtil;
import cn.kgc.grace.result.GraceJSONResult;
import cn.kgc.utils.ExcludeProperties;
import cn.kgc.utils.IPUtil;
import cn.kgc.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static cn.kgc.base.BaseInfoProperties.PREFIX_BLACK;
import static cn.kgc.base.BaseInfoProperties.PREFIX_LIMIT;

/**
 * @author YC
 * 黑名单过滤器
 * 设计：若某个IP地址触发了黑名单规则，限制 1访问全部接口/2当前访问触发规则的接口（此项目选择2）
 */
@Component
public class BlackIpFilter implements GlobalFilter, Ordered {

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private ExcludeProperties excludeProperties;
    /**
     * 路径匹配规则器
     */
    private final AntPathMatcher MATCHER = new AntPathMatcher();

    @Value("${blackIp.continueCounts}")
    private Integer continueCounts;
    @Value("${blackIp.timeInterval}")
    private Integer timeInterval;
    @Value("${blackIp.limitTimes}")
    private Integer limitTimes;

    /**
     * 将错误信息封装到响应中
     */
    private Mono<Void> renderErrorMsg(ServerWebExchange exchange) {
        //获得响应信息
        ServerHttpResponse response = exchange.getResponse();
        //设置响应状态码
        response.setStatusCode(HttpStatus.OK);
        //设置响应的格式是JSON格式的字符串
        response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
        //设置响应信息
        String jsonStr = JSONUtil.toJsonStr(GraceJSONResult.errorMsg("您访问频率过快,请稍后再试"));
        //将响应信息写入到响应流中
        DataBuffer dataBuffer = response.bufferFactory().wrap(jsonStr.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(dataBuffer));
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求访问的接口信息，判断是否在需要限流的名单中，不需要限流的接口直接放行
        String methodApi = exchange.getRequest().getPath().toString();
        List<String> ipLimitUrls = excludeProperties.getIpLimitUrls();
        boolean isExclude = ipLimitUrls.stream().anyMatch(url -> MATCHER.match(url, methodApi));
        if (!isExclude) {
            return chain.filter(exchange);
        }
        // 获取请求的IP地址
        String clientIp = IPUtil.getIP(exchange.getRequest());
        String blackKey = PREFIX_BLACK + clientIp + ":" + methodApi;
        String limitKey = PREFIX_LIMIT + clientIp + ":" + methodApi;
        // 判断IP访问某个接口的操作是否已经在黑名单中，在 则直接限流，返回信息
        if (redisUtil.hasKey(blackKey)) {
            return renderErrorMsg(exchange);
        }
        // 不在 则将此IP地址访问接口的次数+1（并且如果是第一次访问则设置一个过期时间）
        long limitCount = redisUtil.increment(limitKey);
        if (limitCount == 1) {
            redisUtil.expire(limitKey, timeInterval, TimeUnit.SECONDS);
        }
        // 若在限制的时间范围内，访问次数达到黑名单的次数限制，则将此IP地址加入黑名单并设置过期时间
        if (limitCount >= continueCounts) {
            redisUtil.set(blackKey, "", limitTimes, TimeUnit.SECONDS);
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

}
